package com.corpgovernment.common.requestprocess;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

import com.corpgovernment.common.base.JSONResult;
import com.google.common.collect.Maps;

import lombok.extern.slf4j.Slf4j;

/**
 * 客户端调用工具
 * 
 * @author dlz
 * @since 2024/3/19
 */
@Slf4j
public final class ClientInvokeUtil {
    private ClientInvokeUtil() {}

    /**
     * 调用接口
     * 
     * @param invokeFunc 接口调用函数
     * @param req 请求
     * @param interfaceName 接口名
     * @param core 是否为核心依赖方式调用，如果接口未调用成功则会抛出异常中断流程,非核心的情况则返回null
     * @return 响应
     */
    public static <Req, Res> Res invoke(Function<Req, JSONResult<Res>> invokeFunc, Req req, String interfaceName,
        boolean core) {
        return invoke(invokeFunc, req, interfaceName, true, Collections.emptyMap());
    }

    /**
     * 以核心依赖方式调用接口，如果接口未调用成功则会抛出异常中断流程
     * 
     * @param invokeFunc 接口调用函数
     * @param req 请求
     * @param interfaceName 接口名
     * @return 响应
     */
    public static <Req, Res> Res invokeCore(Function<Req, JSONResult<Res>> invokeFunc, Req req, String interfaceName) {
        return invoke(invokeFunc, req, interfaceName, true);
    }

    /**
     * 以非核心依赖方式调用接口，如果接口未调用成功也不会抛出异常
     * 
     * @param invokeFunc 接口调用函数
     * @param req 请求
     * @param interfaceName 接口名
     * @return 响应
     */
    public static <Req, Res> Optional<Res> invokeNotCore(Function<Req, JSONResult<Res>> invokeFunc, Req req,
        String interfaceName) {
        return Optional.ofNullable(invoke(invokeFunc, req, interfaceName, false));
    }

    /**
     * 调用服务商接口
     *
     * @param invokeFunc 接口调用函数
     * @param supplierCode 供应商code
     * @param interfaceName 接口名
     * @param core 是否为核心依赖方式调用，如果接口未调用成功则会抛出异常中断流程,非核心的情况则返回null
     * @return 响应
     */
    public static <Res> Res invokeSupplier(Supplier<Res> invokeFunc, String interfaceName,
        String supplierCode, boolean core) {

        Map<String, String> customTag = Maps.newHashMapWithExpectedSize(1);
        customTag.put("supplierCode", supplierCode);

        return invoke(req -> JSONResult.success(invokeFunc.get()), null, interfaceName, core, customTag);
    }

    /**
     * 以核心依赖方式调用服务商接口，如果接口未调用成功则会抛出异常中断流程
     *
     * @param invokeFunc 接口调用函数
     * @param supplierCode 供应商code
     * @param interfaceName 接口名
     * @return 响应
     */
    public static <Res> Res invokeSupplierCore(Supplier<Res> invokeFunc, String interfaceName,
        String supplierCode) {
        return invokeSupplier(invokeFunc, interfaceName, supplierCode, true);
    }

    /**
     * 以非核心依赖方式调用服务商接口，如果接口未调用成功也不会抛出异常
     *
     * @param invokeFunc 接口调用函数
     * @param supplierCode 供应商code
     * @param interfaceName 接口名
     * @return 响应
     */
    public static <Res> Optional<Res> invokeSupplierNotCore(Supplier<Res> invokeFunc, String interfaceName,
        String supplierCode) {
        return Optional.ofNullable(invokeSupplier(invokeFunc, interfaceName, supplierCode, false));
    }

    private static <Req, Res> Res invoke(Function<Req, JSONResult<Res>> invokeFunc, Req req,
        String interfaceName, boolean core, Map<String, String> customTag) {

        JSONResult<Res> result = null;
        Exception ex = null;
        LocalDateTime startTime = LocalDateTime.now();
        try {
            result = invokeFunc.apply(req);
        } catch (Exception e) {
            ex = e;
            log.error("外部" + (core ? "" : "非") + "核心接口调用异常:" + interfaceName, e);
        } finally {
            // 响应日志埋点
            responseLogMetric(interfaceName, req, result, ex, startTime, customTag);
        }

        if (core && (result == null || result.getData() == null)) {
            throw new RuntimeException("外部接口调用异常：" + interfaceName);
        }

        return Optional.ofNullable(result).map(JSONResult::getData).orElse(null);
    }

    /**
     * 响应日志埋点
     * 
     * @param req 请求
     * @param result 执行结果
     * @param ex 异常
     * @param startTime 开始时间
     */
    private static <Req, Res> void responseLogMetric(String interfaceName, Req req, JSONResult<Res> result,
        Exception ex, LocalDateTime startTime, Map<String, String> customTag) {
        try {
            Duration duration = Duration.between(startTime, LocalDateTime.now());
            // 埋点处理
            doMetric(interfaceName, req, result, ex, duration, customTag);
        } catch (Throwable e) {
            log.error(interfaceName + "外部接口调用埋点失败", e);
        }
    }

    /**
     * 执行响应埋点
     */
    private static <Req, Res> void doMetric(String interfaceName, Req req, JSONResult<Res> result, Exception ex,
        Duration duration, Map<String, String> customTag) {

        MetricUtil.build("client.invoke.response")
            .tag("exceptionName",
                Optional.ofNullable(ex).map(Exception::getClass).map(Class::getName).orElse(null))
            .tag("actionName", LogContextUtil.getActionName())
            .tag("interfaceName", interfaceName)
            .tag("success",
                LogUtils.boolToStr(Optional.ofNullable(result).map(JSONResult::getData).isPresent()))
            .tag(customTag)
            .recordByTimer((int)duration.toMillis());
    }
}
